/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.api.vfs;

import com.google.common.io.ByteStreams;

import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.vfs.util.NotClosableInputStream;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class TarArchiver extends Archiver {
    public TarArchiver(VirtualFile folder) {
        super(folder);
    }

    @Override
    public void compress(OutputStream tarOutput) throws IOException, ServerException {
        compress(tarOutput, VirtualFileFilter.ACCEPT_ALL);
    }

    @Override
    public void compress(OutputStream tarOutput, VirtualFileFilter filter) throws IOException, ServerException {
        try (TarArchiveOutputStream tarOutputStream = new TarArchiveOutputStream(tarOutput)) {
            tarOutputStream.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
            folder.accept(new VirtualFileVisitor() {
                @Override
                public void visit(VirtualFile visitedVirtualFile) throws ServerException {
                    if (filter.accept(visitedVirtualFile)) {
                        if (!visitedVirtualFile.equals(folder)) {
                            addTarEntry(visitedVirtualFile, tarOutputStream);
                        }
                        if (visitedVirtualFile.isFolder()) {
                            for (VirtualFile child : visitedVirtualFile.getChildren()) {
                                child.accept(this);
                            }
                        }
                    }
                }
            });
        }
    }

    private String getTarEntryName(VirtualFile virtualFile) {
        Path tarPath = virtualFile.getPath().subPath(folder.getPath());
        if (virtualFile.isFolder()) {
            return tarPath.toString() + '/';
        }
        return tarPath.toString();
    }

    private void addTarEntry(VirtualFile virtualFile, TarArchiveOutputStream tarOutputStream) throws ServerException {
        try {
            TarArchiveEntry tarEntry = new TarArchiveEntry(getTarEntryName(virtualFile));
            if (virtualFile.isFolder()) {
                tarEntry.setModTime(0);
                tarOutputStream.putArchiveEntry(tarEntry);
            } else {
                tarEntry.setSize(virtualFile.getLength());
                tarEntry.setModTime(virtualFile.getLastModificationDate());
                tarOutputStream.putArchiveEntry(tarEntry);
                try (InputStream content = virtualFile.getContent()) {
                    ByteStreams.copy(content, tarOutputStream);
                }
            }
            tarOutputStream.closeArchiveEntry();
        } catch (ForbiddenException e) {
            throw new ServerException(e.getServiceError());
        } catch (IOException e) {
            throw new ServerException(e.getMessage(), e);
        }
    }

    @Override
    public void extract(InputStream tarInput, boolean overwrite, int stripNumber)
            throws IOException, ForbiddenException, ConflictException, ServerException {
        try (TarArchiveInputStream tarInputStream = new TarArchiveInputStream(tarInput)) {
            InputStream notClosableInputStream = new NotClosableInputStream(tarInputStream);
            TarArchiveEntry tarEntry;
            while ((tarEntry = tarInputStream.getNextTarEntry()) != null) {
                VirtualFile extractFolder = folder;

                Path relativePath = Path.of(tarEntry.getName());

                if (stripNumber > 0) {
                    if (relativePath.length() <= stripNumber) {
                        continue;
                    }
                    relativePath = relativePath.subPath(stripNumber);
                }

                if (tarEntry.isDirectory()) {
                    if (!extractFolder.hasChild(relativePath)) {
                        extractFolder.createFolder(relativePath.toString());
                    }
                    continue;
                }

                if (relativePath.length() > 1) {
                    Path neededParentPath = relativePath.getParent();
                    VirtualFile neededParent = extractFolder.getChild(neededParentPath);
                    if (neededParent == null) {
                        neededParent = extractFolder.createFolder(neededParentPath.toString());
                    }
                    extractFolder = neededParent;
                }

                String fileName = relativePath.getName();
                VirtualFile file = extractFolder.getChild(Path.of(fileName));
                if (file == null) {
                    extractFolder.createFile(fileName, notClosableInputStream);
                } else {
                    if (overwrite) {
                        file.updateContent(notClosableInputStream);
                    } else {
                        throw new ConflictException(String.format("File '%s' already exists", file.getPath()));
                    }
                }
            }
        }
    }
}
